package yz.cp.back.auth.security;

import com.google.common.base.Charsets;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.apache.commons.io.IOUtils;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import yz.cp.back.common.integration.IntegrationAuthentication;
import yz.cp.back.common.integration.IntegrationAuthenticationContext;
import yz.cp.back.common.integration.authenticator.IntegrationAuthenticator;
import yz.cp.back.common.utils.SpringUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

public class ImAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private String usernameParameter = "username";
    private String passwordParameter = "password";
    private String authType="authType";
    private boolean postOnly = true;
    public static Collection<IntegrationAuthenticator> authenticators;
    private Table<String,String,String> authTable= HashBasedTable.create();

    public ImAuthenticationFilter(RequestMatcher requestMatcher) {
        super(requestMatcher);
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (!this.requiresAuthentication(request, response)) {
            chain.doFilter(request, response);
        }else {
            try {
                super.doFilter(req, res, chain);
            } finally {
                IntegrationAuthenticationContext.clear();
            }
        }
    }

    /**
     * 添加未认证用户认证信息，然后在provider里面进行正式认证
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @return
     * @throws AuthenticationException
     * @throws IOException
     * @throws ServletException
     */


    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
            throws AuthenticationException, IOException, ServletException {
        if (postOnly && !httpServletRequest.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + httpServletRequest.getMethod());
        }
        String username = this.obtainUsername(httpServletRequest);
        String password = this.obtainPassword(httpServletRequest);
        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        //设置集成登录信息
        IntegrationAuthentication integrationAuthentication = new IntegrationAuthentication();
        integrationAuthentication.setAuthType(httpServletRequest.getParameter(this.authType));
        if(StringUtils.isEmpty(integrationAuthentication.getAuthType())){
            String url = httpServletRequest.getServletPath();
            if (httpServletRequest.getPathInfo() != null) {
                url = url + httpServletRequest.getPathInfo();
            }

            if(authTable.containsRow(url)){
                Set<Map.Entry<String,String>> setEntry=authTable.row(url).entrySet();
                for(Map.Entry<String,String> entry:setEntry){
                    integrationAuthentication.setAuthType(entry.getKey());
                    integrationAuthentication.setProduce(entry.getValue());
                    break;
                }
            }
        }
        integrationAuthentication.setAuthParameters(httpServletRequest.getParameterMap());
        integrationAuthentication.setBody(IOUtils.toString(httpServletRequest.getInputStream(), Charsets.UTF_8));
        ImAuthenticationToken authRequest = new ImAuthenticationToken(username,password);
        prepare(integrationAuthentication);
        if(StringUtils.isEmpty(password)&&StringUtils.hasText(integrationAuthentication.getCredentials())){
            authRequest = new ImAuthenticationToken(integrationAuthentication.getUsername(),integrationAuthentication.getCredentials());
        }
        IntegrationAuthenticationContext.set(integrationAuthentication);
        // Allow subclasses to set the "details" property
        setDetails(httpServletRequest, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    private void setDetails(HttpServletRequest request,ImAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }
    /**
     * 进行预处理
     * @param integrationAuthentication
     */
    private void prepare(IntegrationAuthentication integrationAuthentication) {

        //延迟加载认证器
        if(this.authenticators == null){
            synchronized (this){
                Map<String, IntegrationAuthenticator> integrationAuthenticatorMap = SpringUtil.getApplicationContext().getBeansOfType(IntegrationAuthenticator.class);
                if(integrationAuthenticatorMap != null){
                    this.authenticators = integrationAuthenticatorMap.values();
                }
            }
        }

        if(this.authenticators == null){
            this.authenticators = new ArrayList<>();
        }

        for (IntegrationAuthenticator authenticator: authenticators) {
            if(authenticator.support(integrationAuthentication)){
                authenticator.prepare(integrationAuthentication);
            }
        }
    }


    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }
    public void setUsernameParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.usernameParameter = usernameParameter;
    }

    public void setPasswordParameter(String passwordParameter) {
        Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
        this.passwordParameter = passwordParameter;
    }


    public String getAuthType() {
        return authType;
    }

    public void setAuthType(String authType) {
        Assert.hasText(passwordParameter, "authType parameter must not be empty or null");
        this.authType = authType;
    }

    public final String getUsernameParameter() {
        return this.usernameParameter;
    }

    public final String getPasswordParameter() {
        return this.passwordParameter;
    }
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(this.passwordParameter);
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(this.usernameParameter);
    }

    protected String obtainAuthType(HttpServletRequest request) {
        return request.getParameter(this.authType);
    }

    public Table<String, String, String> getAuthTable() {
        return authTable;
    }

    public void setAuthTable(Table<String, String, String> authTable) {
        this.authTable = authTable;
    }
}